
//////////////////////////////////////////////////////////////////////////
//	JPG
//////////////////////////////////////////////////////////////////////////
#if KIMAGE_JPG
class KImageFormatJPG
{

};
#else

extern "C" {
	#include <jpeglib.h>
	#include <jerror.h>
}

#ifdef _WIN32
	#pragma comment(lib,"jpeg.lib")
#endif

/*
 */
class ImageFileJPEG {
		
		ImageFileJPEG();
		
	public:
		
		// load image
		static int load(Image &image,const char *name);
		static int load(Image &image,const unsigned char *data,int size);
		
		// save image
		static int save(const Image &image,const char *name,float quality);
		
	private:
		
		static void error_exit(j_common_ptr common);
		static void output_message(j_common_ptr common);
		
		static void init_source(j_decompress_ptr decompress);
		static void term_source(j_decompress_ptr decompress);
		
		static boolean file_fill_input_buffer(j_decompress_ptr decompress);
		static void file_skip_input_data(j_decompress_ptr decompress,long num_bytes);
		
		static boolean data_fill_input_buffer(j_decompress_ptr decompress);
		static void data_skip_input_data(j_decompress_ptr decompress,long num_bytes);
		
		static void file_init_destination(j_compress_ptr compress);
		static boolean file_empty_output_buffer(j_compress_ptr compress);
		static void file_term_destination(j_compress_ptr compress);
		
		struct ErrorData {
			jpeg_error_mgr pub;
			jmp_buf setjmp_buffer;
		};
		
		struct FileSource {
			jpeg_source_mgr pub;
			JOCTET buffer[4096];
			File *file;
		};
		
		struct DataSource {
			jpeg_source_mgr pub;
			JOCTET buffer[4096];
			const unsigned char *ptr;
			const unsigned char *src;
			int size;
		};
		
		struct FileDestination {
			jpeg_destination_mgr pub;
			JOCTET buffer[4096];
			File *file;
		};
};

/*
 */
ImageFileJPEG::ImageFileJPEG() {
	
}

/*
 */
void ImageFileJPEG::error_exit(j_common_ptr common) {
	ErrorData *data = (ErrorData*)common->err;
	(*common->err->output_message)(common);
	longjmp(data->setjmp_buffer,1);
}

void ImageFileJPEG::output_message(j_common_ptr common) {
	char buf[JMSG_LENGTH_MAX];
	(*common->err->format_message)(common,buf);
	Log::error("ImageFileJPEG::output_message(): %s\n",buf);
}

/*
 */
void ImageFileJPEG::init_source(j_decompress_ptr decompress) {
	
}

void ImageFileJPEG::term_source(j_decompress_ptr decompress) {
	
}

/*
 */
boolean ImageFileJPEG::file_fill_input_buffer(j_decompress_ptr decompress) {
	FileSource *source = (FileSource*)decompress->src;
	size_t bytes = source->file->read(source->buffer,sizeof(char),sizeof(source->buffer));
	if(bytes <= 0) {
		if(source->file->tell() == 0) ERREXIT(decompress,JERR_INPUT_EMPTY);
		WARNMS(decompress,JWRN_JPEG_EOF);
		source->buffer[0] = (JOCTET)0xff;
		source->buffer[1] = (JOCTET)JPEG_EOI;
		bytes = 2;
	}
	source->pub.next_input_byte = source->buffer;
	source->pub.bytes_in_buffer = bytes;
	return true;
}

void ImageFileJPEG::file_skip_input_data(j_decompress_ptr decompress,long num_bytes) {
	FileSource *source = (FileSource*)decompress->src;
	if(num_bytes > 0) {
		while(num_bytes > (long)source->pub.bytes_in_buffer) {
			num_bytes -= (long)source->pub.bytes_in_buffer;
			file_fill_input_buffer(decompress);
		}
		source->pub.next_input_byte += num_bytes;
		source->pub.bytes_in_buffer -= num_bytes;
	}
}

/*
 */
int ImageFileJPEG::load(Image &image,const char *name) {
	
	File file;
	if(file.open(name,"rb") == 0) {
		Log::error("ImageFileJPEG::load(): can't open \"%s\" file\n",name);
		return 0;
	}
	
	// create jpeg decompress
	ErrorData error;
	jpeg_decompress_struct decompress;
	decompress.err = jpeg_std_error(&error.pub);
	error.pub.error_exit = error_exit;
	error.pub.output_message = output_message;
	if(setjmp(error.setjmp_buffer)) {
		Log::error("ImageFileJPEG::load(): can't load \"%s\" file\n",name);
		jpeg_destroy_decompress(&decompress);
		file.close();
		return 0;
	}
	
	jpeg_create_decompress(&decompress);
	
	decompress.src = (jpeg_source_mgr*)(*decompress.mem->alloc_small)((j_common_ptr)&decompress,JPOOL_PERMANENT,sizeof(FileSource));
	FileSource *source = (FileSource*)decompress.src;
	source->pub.init_source = init_source;
	source->pub.fill_input_buffer = file_fill_input_buffer;
	source->pub.skip_input_data = file_skip_input_data;
	source->pub.resync_to_restart = jpeg_resync_to_restart;
	source->pub.term_source = term_source;
	source->pub.bytes_in_buffer = 0;
	source->pub.next_input_byte = NULL;
	source->file = &file;
	
	jpeg_read_header(&decompress,true);
	jpeg_start_decompress(&decompress);
	
	// create image
	if(decompress.output_components == 1) image.create2D(decompress.image_width,decompress.image_height,Image::FORMAT_R8,1,0);
	else if(decompress.output_components == 3) image.create2D(decompress.image_width,decompress.image_height,Image::FORMAT_RGB8,1,0);
	else {
		Log::error("ImageFileJPEG::load(): unsupported format in \"%s\" file\n",name);
		jpeg_finish_decompress(&decompress);
		jpeg_destroy_decompress(&decompress);
		file.close();
		return 0;
	}
	
	// load jpeg image
	unsigned char *data = image.getPixels2D();
	int stride = decompress.output_width * decompress.output_components;
	while(decompress.output_scanline < decompress.output_height) {
		jpeg_read_scanlines(&decompress,&data,1);
		data += stride;
	}
	
	jpeg_finish_decompress(&decompress);
	jpeg_destroy_decompress(&decompress);
	
	file.close();
	
	return 1;
}

/*
 */
boolean ImageFileJPEG::data_fill_input_buffer(j_decompress_ptr decompress) {
	DataSource *source = (DataSource*)decompress->src;
	size_t bytes = source->src + source->size - source->ptr;
	if(bytes > sizeof(source->buffer)) bytes = sizeof(source->buffer);
	memcpy(source->buffer,source->ptr,sizeof(char) * bytes);
	source->ptr += bytes;
	if(bytes == 0) {
		if(source->ptr == source->src) ERREXIT(decompress,JERR_INPUT_EMPTY);
		WARNMS(decompress,JWRN_JPEG_EOF);
		source->buffer[0] = (JOCTET)0xff;
		source->buffer[1] = (JOCTET)JPEG_EOI;
		bytes = 2;
	}
	source->pub.next_input_byte = source->buffer;
	source->pub.bytes_in_buffer = bytes;
	return true;
}

void ImageFileJPEG::data_skip_input_data(j_decompress_ptr decompress,long num_bytes) {
	DataSource *source = (DataSource*)decompress->src;
	if(num_bytes > 0) {
		while(num_bytes > (long)source->pub.bytes_in_buffer) {
			num_bytes -= (long)source->pub.bytes_in_buffer;
			data_fill_input_buffer(decompress);
		}
		source->pub.next_input_byte += num_bytes;
		source->pub.bytes_in_buffer -= num_bytes;
	}
}

/*
 */
int ImageFileJPEG::load(Image &image,const unsigned char *src,int size) {
	
	if(src == NULL || size < 4) {
		Log::error("ImageFileJPEG::load(): wrong source\n");
		return 0;
	}
	
	// create jpeg decompress
	ErrorData error;
	jpeg_decompress_struct decompress;
	decompress.err = jpeg_std_error(&error.pub);
	error.pub.error_exit = error_exit;
	error.pub.output_message = output_message;
	if(setjmp(error.setjmp_buffer)) {
		Log::error("ImageFileJPEG::load(): can't load image\n");
		jpeg_destroy_decompress(&decompress);
		return 0;
	}
	
	jpeg_create_decompress(&decompress);
	
	decompress.src = (jpeg_source_mgr*)(*decompress.mem->alloc_small)((j_common_ptr)&decompress,JPOOL_PERMANENT,sizeof(DataSource));
	DataSource *source = (DataSource*)decompress.src;
	source->pub.init_source = init_source;
	source->pub.fill_input_buffer = data_fill_input_buffer;
	source->pub.skip_input_data = data_skip_input_data;
	source->pub.resync_to_restart = jpeg_resync_to_restart;
	source->pub.term_source = term_source;
	source->pub.bytes_in_buffer = 0;
	source->pub.next_input_byte = NULL;
	source->ptr = src;
	source->src = src;
	source->size = size;
	
	jpeg_read_header(&decompress,true);
	jpeg_start_decompress(&decompress);
	
	// create image
	if(decompress.output_components == 1) image.create2D(decompress.image_width,decompress.image_height,Image::FORMAT_R8,1,0);
	else if(decompress.output_components == 3) image.create2D(decompress.image_width,decompress.image_height,Image::FORMAT_RGB8,1,0);
	else {
		Log::error("ImageFileJPEG::load(): unsupported format\n");
		jpeg_finish_decompress(&decompress);
		jpeg_destroy_decompress(&decompress);
		return 0;
	}
	
	// load jpeg image
	unsigned char *data = image.getPixels2D();
	int stride = decompress.output_width * decompress.output_components;
	while(decompress.output_scanline < decompress.output_height) {
		jpeg_read_scanlines(&decompress,&data,1);
		data += stride;
	}
	
	jpeg_finish_decompress(&decompress);
	jpeg_destroy_decompress(&decompress);
	
	return 1;
}

/*
 */
void ImageFileJPEG::file_init_destination(j_compress_ptr compress) {
	FileDestination *destination = (FileDestination*)compress->dest;
	destination->pub.next_output_byte = destination->buffer;
	destination->pub.free_in_buffer = sizeof(destination->buffer);
}

boolean ImageFileJPEG::file_empty_output_buffer(j_compress_ptr compress) {
	FileDestination *destination = (FileDestination*)compress->dest;
	size_t bytes = destination->file->write(destination->buffer,1,sizeof(destination->buffer));
	if(bytes != sizeof(destination->buffer)) {
		ERREXIT(compress,JERR_FILE_WRITE);
	}
	destination->pub.next_output_byte = destination->buffer;
	destination->pub.free_in_buffer = sizeof(destination->buffer);
	return true;
}

void ImageFileJPEG::file_term_destination(j_compress_ptr compress) {
	FileDestination *destination = (FileDestination*)compress->dest;
	size_t bytes = sizeof(destination->buffer) - destination->pub.free_in_buffer;
	if(bytes > 0 && destination->file->write(destination->buffer,1,bytes) != bytes) {
		ERREXIT(compress,JERR_FILE_WRITE);
	}
}

/*
 */
int ImageFileJPEG::save(const Image &image,const char *name,float quality) {
	
	if(image.getType() != Image::IMAGE_2D) {
		Log::error("ImageFileJPEG::save(): bad image type %s\n",image.getTypeName());
		return 0;
	}
	
	// create jpeg compress
	ErrorData error;
	jpeg_compress_struct compress;
	compress.err = jpeg_std_error(&error.pub);
	error.pub.error_exit = error_exit;
	error.pub.output_message = output_message;
	if(setjmp(error.setjmp_buffer)) {
		Log::error("ImageFileJPEG::save(): can't save \"%s\" file\n",name);
		return 0;
	}
	jpeg_create_compress(&compress);
	compress.image_width = image.getWidth();
	compress.image_height = image.getHeight();
	
	// select format
	if(image.getFormat() == Image::FORMAT_R8) {
		compress.input_components = 1;
		compress.in_color_space = JCS_GRAYSCALE;
	} else if(image.getFormat() == Image::FORMAT_RGB8) {
		compress.input_components = 3;
		compress.in_color_space = JCS_RGB;
	} else {
		Log::error("ImageFileJPEG::save(): can't save %s format into the \"%s\" file\n",image.getFormatName(),name);
		jpeg_destroy_compress(&compress);
		return 0;
	}
	
	File file;
	if(file.open(name,"wb") == 0) {
		Log::error("ImageFileJPEG::save(): can't create \"%s\" file\n",name);
		return 0;
	}
	
	compress.dest = (jpeg_destination_mgr*)(*compress.mem->alloc_small)((j_common_ptr)&compress,JPOOL_PERMANENT,sizeof(FileDestination));
	FileDestination *destination = (FileDestination*)compress.dest;
	destination->pub.init_destination = file_init_destination;
	destination->pub.empty_output_buffer = file_empty_output_buffer;
	destination->pub.term_destination = file_term_destination;
	destination->file = &file;
	
	jpeg_set_defaults(&compress);
	jpeg_set_quality(&compress,(int)(quality * 100.0f),true);
	
	jpeg_start_compress(&compress,true);
	const unsigned char *data = image.getPixels2D();
	int stride = image.getWidth() * image.getPixelSize();
	while(compress.next_scanline < compress.image_height) {
		JSAMPROW ptr = (JSAMPROW)&data[compress.next_scanline * stride];
		jpeg_write_scanlines(&compress,&ptr,1);
	}
	jpeg_finish_compress(&compress);
	
	jpeg_destroy_compress(&compress);
	
	file.close();
	
	return 1;
}


#endif
